package se.thoka.twt.client.utils;

import java.io.Serializable;
import java.util.Date;

import com.google.gwt.user.client.rpc.IsSerializable;

/**
 * <p>
 * The <code>GWTDate</code> bean represents a <code>org.joda.time.LocalDate</code> object in the GWT application. It performs no validation of its values.
 * </p>
 * <p>
 * A <code>GWTDate</code> is used to represent the date component of a {@link GWTDateTime}.
 * </p>
 * 
 * @author Thobias Karlsson, Thoka
 * @since 9 jan 2010
 */
@SuppressWarnings("deprecation")
public class GWTDate implements IsSerializable, Serializable, SerializableType<String>, Comparable<GWTDate> {

	private static final long serialVersionUID = -6722372267906343732L;
	/**
	 * The year, e.g. 1986.
	 */
	private int year;
	/**
	 * The month of the year, 1 represents January.
	 */
	private int monthOfYear;
	/**
	 * The day of month.
	 */
	private int dayOfMonth;

	/**
	 * Creates a new instance of <code>GWTDate</code>.
	 */
	public GWTDate() {
		this(0, 0, 0);
	}

	/**
	 * Creates a new instance of <code>GWTDate</code>.
	 * 
	 * @param year The year, e.g. 1986.
	 * @param monthOfYear The month of the year, where 1 represents January.
	 * @param dayOfMonth The day of the month.
	 */
	public GWTDate(int year, int monthOfYear, int dayOfMonth) {
		super();
		setYear(year);
		setMonthOfYear(monthOfYear);
		setDayOfMonth(dayOfMonth);
	}

	/**
	 * Creates a new instance of <code>GWTDate</code>.
	 * 
	 * @param gwtDate A <code>GWTDate</code> to copy. Must not be <code>null</code>.
	 */
	public GWTDate(GWTDate gwtDate) {
		this(gwtDate.getYear(), gwtDate.getMonthOfYear(), gwtDate.getDayOfMonth());
	}

	/**
	 * Creates a new instance of <code>GWTDate</code>.
	 * 
	 * @param date A {@link Date}. Must not be <code>null</code>.
	 */
	public GWTDate(Date date) {
		this(date.getYear() + 1900, date.getMonth() + 1, date.getDate());
	}

	/**
	 * Returns the day of the month.
	 * 
	 * @return Returns the day of the month.
	 */
	public int getDayOfMonth() {
		return this.dayOfMonth;
	}

	/**
	 * Returns the month of the year.
	 * 
	 * @return Returns the month.
	 */
	public int getMonthOfYear() {
		return this.monthOfYear;
	}

	/**
	 * Returns the year.
	 * 
	 * @return Returns the year.
	 */
	public int getYear() {
		return this.year;
	}

	/**
	 * Sets the day of the month.
	 * 
	 * @param dayOfMonth The new day of the month.
	 */
	public void setDayOfMonth(int dayOfMonth) {
		this.dayOfMonth = dayOfMonth;
	}

	/**
	 * Sets the month of the year.
	 * 
	 * @param monthOfYear The new month of the year.
	 */
	public void setMonthOfYear(int monthOfYear) {
		this.monthOfYear = monthOfYear;
	}

	/**
	 * Sets the year.
	 * 
	 * @param year The new year.
	 */
	public void setYear(int year) {
		this.year = year;
	}

	@Override
	public String toString() {
		return writeSerial();
	}

	/**
	 * Converts this <code>GWTDate</code> into a {@link GWTDateTime} at the supplied {@link GWTTime}.
	 * 
	 * @param time The {@link GWTTime}. Must not be <code>null</code>.
	 * @return A new {@link GWTDateTime}.
	 */
	public GWTDateTime toGWTDateTime(GWTTime time) {
		return new GWTDateTime(new GWTDate(this), new GWTTime(time));
	}

	@Override
	public boolean equals(Object obj) {
		return (obj == this || (obj != null && obj.hashCode() == hashCode()));
	}

	@Override
	public int hashCode() {
		return (157 + 19 * getYear() + 23 * getMonthOfYear() + 29 * getDayOfMonth());
	}

	/**
	 * Compares this <code>GWTDate</code> to another, returning true if this date is strictly after the other date.
	 * 
	 * @param date The date to compare against.
	 * @return <code>true</code> if this date is after the <code>date</code> parameter.
	 */
	public boolean isAfter(GWTDate date) {
		boolean result = false;
		if (!equals(date)) {
			// If we're not equal we are the inverse of isBefore.
			result = !isBefore(date);
		}
		return result;
	}

	/**
	 * Compares this <code>GWTDate</code> to another, returning true if this date is strictly before the other date.
	 * 
	 * @param date The date to compare against.
	 * @return <code>true</code> if this date is before the <code>date</code> parameter.
	 */
	public boolean isBefore(GWTDate date) {
		boolean result = false;
		if (!equals(date)) {
			result = getYear() < date.getYear();
			if (!result && getYear() == date.getYear()) {
				result = getMonthOfYear() < date.getMonthOfYear();
				if (!result && getMonthOfYear() == date.getMonthOfYear()) {
					result = getDayOfMonth() < date.getDayOfMonth();
				}
			}
		}
		return result;
	}

	/**
	 * A date.
	 * 
	 * @return The date.
	 */
	public Date toDate() {
		return new Date(getYear() - 1900, getMonthOfYear() - 1, getDayOfMonth());
	}

	@Override
	public int compareTo(GWTDate o) {
		return toDate().compareTo(o == null ? null : o.toDate());
	}
	
	@Override
	public void readSerial(String serial) {
		String[] split = serial.split("-");
		if (split.length == 3) {
			setYear(ParseUtil.tryParse(split[0], 0));
			setMonthOfYear(ParseUtil.tryParse(split[1], 0));
			setDayOfMonth(ParseUtil.tryParse(split[2], 0));
		} else {
			throw new IllegalArgumentException("Illegal format for serial \"" + serial + "\"");
		}
	}
	
	@Override
	public String writeSerial() {
		StringBuilder sb = new StringBuilder();
		sb.append(getYear());
		sb.append("-");
		sb.append(StringUtils.zeroPad(getMonthOfYear(), 2));
		sb.append("-");
		sb.append(StringUtils.zeroPad(getDayOfMonth(), 2));
		
		return sb.toString();
	}
	
	/**
	 * @return Todays date.
	 */
	public static GWTDate today() {
		return new GWTDate(new Date());
	}
}
